<!-- 基础的crud组件，适用于定制化不高的增删改查页面 -->
<template>
  <avue-crud v-model="formData" v-model:search="searchData" v-model:page="pageData" v-bind="attrs" :option="initOption"
    :table-loading="loading" :data="tableData" :before-open="handleBeforeOpen" @select="handleSelect"
    @selection-change="handleChangeSelect" @size-change="handleChangeSize" @current-change="handleChangeCurrent"
    @search-change="handleSearch" @search-reset="handleReset" @refresh-change="getList" @row-save="handleAddSave"
    @row-update="handleEditSave" @row-del="handleDel" ref="crudRef" :class="selectType === 'radio' && 'radio_crud'">
    <template v-for="slot in slotList" :key="slot.name" #[slot.name]="data">
      <slot :name="slot.name" v-bind="data" :formData="formData" :searchData="searchData"
        :handleChangeStatus="handleChangeStatus"></slot>
    </template>
    <template #menu="data">
      <el-button v-if="initOption.statusBtn" type="primary" link icon="TurnOff" @click="handToggleStatus(data.row)">{{
        data.row.status === 'Disabled' ? '启用' : '停用' }}</el-button>
      <slot name="menu" v-bind="data" :formData="formData" :handleChangeStatus="handleChangeStatus"></slot>
    </template>
    <template v-if="statusTagProp" #[statusTagProp]="{ column, row }">
      <basic-tag :row="row" :column="column" :statusMap="statusMap"></basic-tag>
    </template>
  </avue-crud>
</template>

<script setup>
import { ElMessage } from 'element-plus'
import { computed, nextTick, onMounted, useAttrs, watch, ref } from 'vue'
import emitter from '@/utils/mitt.js'
import { useRoute } from 'vue-router'
import enumData from '@/config/enum.js'
import { request } from '@/utils/request.js'
import { useFlowStart } from './hooks/flowStart'

// 获取当前实例
const that = getCurrentInstance()
const attrs = useAttrs()
const emits = defineEmits(['selection-change', 'formDataChange', 'select'])

// 定义 props
const props = defineProps({
  // avue组件参数
  option: {
    type: Object,
    default: () => ({}),
  },
  // 业务地址，crud接口会根据此生成
  // 例如 a/b/list
  // 那么businessUrl则为a/b
  businessUrl: String,
  // 默认查询参数
  defaultListParams: {
    type: Object,
    default: () => ({
      orderBy: 'createTime',
      orderMethod: 'desc',
    }),
  },
  //列表请求的额外参数
  apiListParams: {
    type: Object,
    default: () => ({}),
  },
  // 新增请求的额外参数
  apiAddParams: {
    type: Object,
    default: () => ({}),
  },
  // 编辑请求的额外参数
  apiEditParams: {
    type: Object,
    default: () => ({}),
  },
  // 删除请求的额外参数
  apiDelParams: {
    type: Object,
    default: () => ({}),
  },
  // 请求接口来源
  apiSource: {
    type: [String, Object],
    default: undefined,
  },
  // 选择类型
  // multiple多选
  // radio单选
  // none不选
  selectType: {
    type: String,
    default: 'none',
  },
  // 自动请求列表数据
  autoLoad: {
    type: Boolean,
    default: true,
  },
  // 删除数据的提示信息
  delTip: {
    type: String,
    default: '确认删除该条数据？',
  },
  // 勾选key，用于勾线后回显自动勾选
  selectedKey: {
    type: String,
    default: 'id',
  },
  // 分页配置
  pageData: {
    type: Object,
    default: () => ({}),
  },
  // 打开之前
  beforeOpen: Function,
  // 生命周期钩子
  beforeGetList: Function, //获取列表之前，可以处理请求参数
  afterGetList: Function, //获取列表之后，可以处理列表数据
  afterGetListSuccess: Function, //列表数据已经展示完之后
  afterGetListError: Function, //请求失败
  beforeAdd: Function,
  afterAdd: Function,
  afterAddSuccess: Function,
  afterAddError: Function,
  beforeEdit: Function,
  afterEdit: Function,
  afterEditSuccess: Function,
  afterEditError: Function,
  beforeDel: Function,
  afterDel: Function,
  afterDelSuccess: Function,
  afterDelError: Function,
})

// 默认选中的表格数据
const defaultSelectList = defineModel('defaultSelectList', { default: [] })
const slots = useSlots()
const noIncludesSlot = ['menu']

// 插槽列表
const slotList = computed(() => {
  if (slots) {
    return Object.keys(slots)
      .filter((item) => !noIncludesSlot.includes(item))
      .map((t) => ({ name: t, slot: slots[t] }))
  }
  return []
})

// 自动生成接口数据
const getApiFun = (type) => {
  const apiAdd = (data, loading) => {
    return request({
      url: props.businessUrl + '/add',
      method: 'post',
      data,
      loading
    })
  }
  const apiEdit = (data, loading) => {
    return request({
      url: props.businessUrl + '/edit',
      method: 'put',
      data,
      loading
    })
  }
  const apiDel = (ids) => {
    return request({
      url: props.businessUrl + '/del',
      method: 'delete',
    })
  }
  const apiGetList = (params, loading) => {
    return request({
      url: props.businessUrl + '/list',
      method: 'get',
      params,
      loading
    })
  }
  const apiGetCode = (params, loading) => {
    return request({
      url: props.businessUrl + '/produceBeforeCode',
      method: 'get',
      params,
      loading
    })
  }
  const apiChangeStatus = () => {
    return request({
      url: props.businessUrl + '/changeStatus',
      method: 'post',
      data,
      loading,
    })
  }
  if (type === 'add') {
    return apiAdd
  }
  if (type === 'edit') {
    return apiEdit
  }
  if (type === 'del') {
    return apiDel
  }
  if (type === 'list') {
    return apiGetList
  }
  if (type === 'code') {
    return apiGetCode
  }
  if (type === 'changeStatus') {
    return apiChangeStatus
  }
  return {
    apiAdd,
    apiEdit,
    apiDel,
    apiGetList,
    apiGetCode,
    apiChangeStatus
  }
}

// 合并请求方法（有些方法可能不是规定的那些CRUD）
const getApiMap = () => {
  let apiMap = getApiFun()
  const route = useRoute()
  const curPath = route.path
  if (typeof props.apiSource === 'object') {
    apiMap = { ...apiMap, ...props.apiSource }
  } else if (typeof props.apiSource === 'string') {
    let modules = import.meta.glob('../../../apis/**/*.js', { eager: true })
    let newApi = modules[`../../../apis${props.apiSource}.js`]
    apiMap = { ...apiMap, ...newApi }
  }
  return apiMap
}

const {
  apiGetList,
  apiAdd,
  apiDel,
  apiEdit,
  apiDisable,
  apiEnable,
  apiGetCode,
  apiChangeStatus,
} = getApiMap()
// 获取code或者禁用
const getOrDisabledCode = async (done, loading) => {
  loading && loading()
  const { afterCode, prefixCode, editFlag } = await apiGetCode()
  initOption.value.column = initOption.value.column.map((item) => {
    if (item.autoCode) {
      if (afterCode === true) {
        item.disabled = true
      } else {
        if (prefixCode) {
          item.value = prefixCode
          if (editFlag === true) {
            item.disabled = false
          } else {
            item.disabled = true
          }
        }
      }
    }
    return item
  })
  done && done()
}
// 默认的option参数
const initOption = ref({
  addBtn: true,
  editBtn: true,
  searchLabelWidth: 120,
  searchMenuSpan: 6,
  menuWidth: 260,
  selection: false,
  labelWidth: 140,
  delBtn: true,
  checkStrictly: true,
  column: [],
})
// 状态字段的prop至
const statusTagProp = ref()
const mergeOption = async () => {
  // 融合本地，传入的option数据
  initOption.value = { ...initOption.value, ...props.option }
  initOption.value.selection = props.selectType !== 'none'
  // 对column进行处理
  for (const item of initOption.value.column) {
    if (item.statusTag) {
      statusTagProp.value = item.prop
    }
    // 是否禁用code
    // 发现给value赋值会有延迟性，怀疑是框架bug，带排查，所以在页面初始化的时候提前调用一次
    if (item.autoCode) {
      getOrDisabledCode()
    }
    if (item.enumCode) {
      item.dicData = enumData[item.enumCode]
    }
    if (item.dicCode) {
      item.dicData = await apiGetEnumByCode(item.dicCode)
    }
  }
}

const apiGetEnumByCode = (code) => {
  return request({
    url: '/obj/enumTypeController/getEnum/' + code,
    method: 'get',
    resField: 'data',
    params: { parentCode: 'all' },
  })
}
mergeOption()
// 弹出打开之前对code进行操作
const handleBeforeOpen = (done, type, loading) => {
  if (
    (type === 'add' || type === 'edit') &&
    initOption.value.column.some((item) => item.autoCode)
  ) {
    getOrDisabledCode(done, loading)
  } else {
    if (typeof props.beforeOpen === 'function') {
      props.beforeOpen(done, type, loading)
    } else {
      done()
    }
  }
}

const crudRef = ref()
// 表单数据
const formData = defineModel({
  type: Object,
  default: () => ({})
})
// 搜索数据
const searchData = ref({})
// 页码数据
const pageData = ref({
  total: 0,
  currentPage: 1,
  pageSize: 10,
  pageSizes: [10, 20, 50, 100, 200, 500],
  ...props.pageData,
})
// 状态映射
const statusMap = ref([])
// 表单数据
const tableData = ref([])
// 表单loading
const loading = ref(false)

// 处理分页大小变化
const handleChangeSize = (val) => {
  pageData.value.pageSize = val
  getList()
}

// 处理当前页码变化
const handleChangeCurrent = (val) => {
  pageData.value.currentPage = val
  getList()
}
// 获取列表
const getList = async (params, done) => {
  const { pageSize, currentPage } = pageData.value
  const searchParams = params || searchData.value
  let resParams = {
    // 如果是树表则默认不分页
    pageSize: props.option.isTableTree ? -1 : pageSize,
    pageNum: currentPage,
    ...props.defaultListParams,
    ...searchParams,
    ...props.apiListParams,
  }
  apiGetList(
    props.beforeGetList ? props.beforeGetList(resParams) : resParams,
    loading
  )
    .then((resData) => {
      const { code, msg, data, total, dicData } = resData
      statusMap.value = dicData
      if (code !== 200) {
        ElMessage.error(msg)
      } else {
        tableData.value =
          (props.afterGetList ? props.afterGetList(data) : data) || []
        pageData.value.total = total
      }
      // 表格勾选回显
      if (props.selectType !== 'none') {
        getSelectedIds()
      }
      props.afterGetListSuccess && props.afterGetListSuccess(err)
    })
    .catch((err) => {
      ElMessage.error(err.msg)
      props.afterGetListError && props.afterGetListError(err)
    })
    .finally((res) => {
      done && done()
    })
}

// 勾选的数据
const selectData = ref([])
// 单选多选时手动钩中
const getSelectedIds = () => {
  // 树结构处理
  const getTableTreeSelectData = (list) => {
    const selectedKey = props.selectedKey
    const ids = defaultSelectList.value.map((item) => item[selectedKey])
    list.forEach((item) => {
      if (ids.includes(item[selectedKey])) {
        selectedDataList.push(item)
      }
      if (
        props.option.isTableTree &&
        Array.isArray(item.children) &&
        item.children.length > 0
      ) {
        getTableTreeSelectData(item.children)
      }
    })
  }
  const selectedDataList = []
  getTableTreeSelectData(tableData.value)
  nextTick(() => {
    crudRef.value?.toggleSelection(selectedDataList, true)
  })
}

// 重置搜索,为了提醒BasicCrudTableSelect组件
const handleReset = (params, done) => {
  emitter.emit('BasicCrudTableSelect_reset' + that.uid)
  getList(params, done)
}

// 单个勾选
const handleSelect = (selection, row) => {
  const selectedKey = props.selectedKey
  const curRow = selection.find(
    (item) => item[selectedKey] === row[selectedKey]
  )
  if (curRow) {
    // 单选就直接赋值就行
    if (props.selectType === 'radio') {
      defaultSelectList.value = [row]
    } else if (props.selectType === 'multiple') {
      defaultSelectList.value.push(row)
    }
  } else {
    const idx = defaultSelectList.value.findIndex(
      (item) => item[selectedKey] === row[selectedKey]
    )
    if (idx !== -1) {
      defaultSelectList.value.splice(idx, 1)
    }
  }
  emits('select', selection, row)
}

// 处理选中项变化
const handleChangeSelect = (selection) => {
  if (props.selectType === 'radio' && selection.length > 1) {
    const preVal = selection.shift()
    crudRef.value.toggleRowSelection(preVal, false)
  }
  emits('selection-change', selection)
}

// 点击搜索按钮
const handleSearch = (params, done) => {
  pageData.value.currentPage = 1
  getList(params, done)
}

// 新增保存
const handleAddSave = async (row, done, loading) => {
  let endParams = { ...row, ...props.apiAddParams }
  let resParams = props.beforeAdd ? props.beforeAdd(endParams) : endParams
  apiAdd(resParams)
    .then(() => {
      ElMessage.success('新增成功！')
      props.afterAddSuccess && props.afterAddSuccess(row)
      done(row)
      getList()
    })
    .catch((err) => {
      props.afterAddError && props.afterAddError(err)
    })
    .finally((res) => {
      loading()
      props.afterAdd && props.afterAdd(res)
    })
}

// 编辑保存
const handleEditSave = async (row, index, done, loading) => {
  let endParams = { ...row, ...props.apiEditParams }
  apiEdit(props.beforeEdit ? props.beforeEdit(endParams) : endParams)
    .then(() => {
      ElMessage.success('修改成功！')
      props.afterEditSuccess && props.afterEditSuccess(row)
      done(row)
      getList()
    })
    .catch((err) => {
      props.afterEditError && props.afterEditError(err)
    })
    .finally(() => {
      loading()
      props.afterEdit && props.afterEdit(res)
    })
}

// 删除
const handleDel = (row) => {
  ElMessageBox.confirm(props.delTip, '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
    beforeClose: async (action, instance, done) => {
      if (action === 'confirm') {
        instance.confirmButtonLoading = true
        apiDel(
          Object.keys(props.apiDelParams).length > 0
            ? { id: row.id, ...props.apiDelParams }
            : row.id
        )
          .then(() => {
            ElMessage.success('删除成功！')
            props.afterDelSuccess && props.afterDelSuccess(row)
            done()
            getList()
          })
          .catch((err) => {
            props.afterDelError && props.afterDelError(err)
          })
          .finally((res) => {
            instance.confirmButtonLoading = false
            props.afterDel && props.afterDel(res)
          })
      } else {
        done()
      }
    },
  })
}
// 切换启用停用状态
const handToggleStatus = async (row) => {
  let apiFun = null
  let msg = ''
  if (row.status === 'Disabled') {
    apiFun = apiEnable
    msg = '启用'
  } else {
    apiFun = apiDisable
    msg = '停用'
  }
  ElMessageBox.confirm(`确认${msg}该数据?`, '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
    beforeClose: async (action, instance, done) => {
      if (action === 'confirm') {
        instance.confirmButtonLoading = true
        apiFun(row)
          .then(() => {
            ElMessage.success(`${msg}成功！`)
            done()
            getList()
          })
          .finally(() => {
            instance.confirmButtonLoading = false
          })
      } else {
        done()
      }
    },
  })
}
// 状态切换
const handleChangeStatus = (row, status) => {
  row._statusChangeLoading = true
  apiChangeStatus({ id: row.id, status })
    .then(() => {
      ElMessage.success(`操作成功！`)
      getList()
    })
    .finally(() => {
      row._statusChangeLoading = false
    })
}

watch(
  () => props.apiListParams,
  () => {
    props.autoLoad && getList()
  },
  {
    deep: true,
    immediate: true,
  }
)

watch(
  () => formData.value,
  (newV) => {
    emits('formDataChange', newV)
  },
  { deep: true, immediate: true }
)
// 监听回显数据
watch(
  () => defaultSelectList.value,
  (newV) => {
    nextTick(() => {
      selectData.value = newV
      crudRef.value?.clearSelection()
      if (props.selectType !== 'none') {
        getSelectedIds()
      }
    })
  },
  { immediate: true, deep: true }
)
defineExpose({
  getList,
  selectData,
  formData,
  searchData,
  pageData,
  tableData,
  crudRef,
  handleChangeStatus
})
</script>

<style lang="scss" scoped>
// 单选隐藏全选按钮
.radio_crud {
  ::v-deep {
    th.el-table-column--selection .cell {
      display: none !important;
    }
  }
}
</style>
